﻿using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.Owin.Security;

using Sustainalytics.FileManager;
using Sustainalytics.LongRunningTaskManager.Interface;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace Sustainalytics.LongRunningTaskManager.Service
{

    public class LongRunningTaskHubManager
    {
        // hub manager instance
        private static readonly Lazy<LongRunningTaskHubManager> _instance = new Lazy<LongRunningTaskHubManager>(() => new LongRunningTaskHubManager());

        private readonly LongRunningTaskManagerHostConfiguration _config;

        // user-connections
        private ConcurrentDictionary<string, LongRunningTaskManagerUser> _hubUsers = new ConcurrentDictionary<string, LongRunningTaskManagerUser>();

        // timer used to trigger updates
        private readonly Timer _updateTimer;

        // task manager
        private readonly ILongRunningTaskManager _taskManager;

        /// <summary>
        /// Hub clients.
        /// </summary>
        private static IHubConnectionContext<dynamic> Clients
        {
            get
            {
                return GlobalHost.ConnectionManager.GetHubContext<LongRunningTaskHub>().Clients;
            }
        }

        /// <summary>
        /// Hub manager instance.
        /// </summary>
        public static LongRunningTaskHubManager Instance
        {
            get
            {
                return _instance.Value;
            }
        }

        /// <summary>
        /// Gets task manager.
        /// </summary>
        public ILongRunningTaskManager TaskManager
        {
            get
            {
                return this._taskManager;
            }
        }


        /// <summary>
        /// Ctor.
        /// </summary>
        private LongRunningTaskHubManager()
        {
            this._config = LongRunningTaskManagerHostConfiguration.Current;
            this._updateTimer = 
                new Timer(SendTaskStatusesToUsers, null, this._config.TaskStatusStartInterval, this._config.TaskStatusUpdateInterval);

            // create task instance snapshot repo
            var taskInstanceSnapshotRepo = new LongRunningTaskInstanceSnapshotRepository(
                connectionString: ConfigurationManager.ConnectionStrings["ClientPlatform"].ConnectionString,
                taskResultFactory: new LongRunningTaskResultFactory());

            
            // file manager
            var fileManager = new DiskFileManager(this._config.DiskFileManagerBaseDirectory);

            // task result transformer
            var taskResultTransformer = new LongRunningTaskFileResultToFileManagerTransformer(fileManager);

            // create task manager
            _taskManager = new LongRunningTaskManager(taskInstanceSnapshotRepo, taskResultTransformer);
        }

        /// <summary>
        /// Send task statuses to users connected to task hub.
        /// </summary>
        /// <param name="state"></param>
        private void SendTaskStatusesToUsers(object state)
        {
            // for each user update task statuses
            foreach (LongRunningTaskManagerUser hubUser in this._hubUsers.ToList().Select(u => u.Value))
            {
                // get overall task instance statuses for specific user
                IEnumerable<LongRunningTaskInstanceStatus> taskInstanceStatusLst = this._taskManager.GetOverallStatus(hubUser.Ticket);

                // get user connection IDs
                IList<string> connectionIds = hubUser.Connections.ToList();

                // push task instance statuses to users
                if (connectionIds != null && connectionIds.Any())
                {
                    Clients.Clients(connectionIds).sendTaskStatusesToUser(taskInstanceStatusLst);                    
                }
            }
        }


        /// <summary>
        /// Add a new connection for user.
        /// </summary>
        /// <param name="username"> Username. </param>
        /// <param name="connectionId"> Connection ID. </param>
        public void AddUserConnection(AuthenticationTicket ticket, string connectionId)
        {
            string username = ticket.Identity.Name;

            if (string.IsNullOrEmpty(username))
            {
                throw new HubManagerUsernameNullOrEmptyException();
            }

            if (string.IsNullOrEmpty(connectionId))
            {
                throw new HubManagerConnectionIdNullOrEmptyException();
            }


            _hubUsers.AddOrUpdate(
                key: username,
                addValue: new LongRunningTaskManagerUser(
                    ticket: ticket,
                    connections: new List<string> { connectionId }),
                updateValueFactory: (string un, LongRunningTaskManagerUser lrtmu) =>
                    {
                        lrtmu.Connections.Add(connectionId);
                        return lrtmu;
                    });
        }

        /// <summary>
        /// Remove user connection.
        /// </summary>
        /// <param name="username"> Username. </param>
        /// <param name="connectionId"> Connection ID. </param>
        public void RemoveUserConnection(AuthenticationTicket ticket, string connectionId)
        {
            string username = ticket.Identity.Name;

            if (string.IsNullOrEmpty(username))
            {
                throw new HubManagerUsernameNullOrEmptyException();
            }

            if (string.IsNullOrEmpty(connectionId))
            {
                throw new HubManagerConnectionIdNullOrEmptyException();
            }


            _hubUsers.AddOrUpdate(
                key: username,
                addValue: new LongRunningTaskManagerUser(
                    ticket: ticket,
                    connections: null),
                updateValueFactory: (string un, LongRunningTaskManagerUser lrtmu) =>
                    {
                        lrtmu.Connections.Remove(connectionId);
                        return lrtmu;
                    });
        }
    }
}
